﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using CoreEx.Common.Extensions;
using CoreEx.Database.Metadata;
using CoreEx.Database.Query;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using System.Collections;

namespace CoreEx.Database.Linq.Implementation
{    
    public class ExpressionTranslator : ExpressionTranslatorBase
    {        
        private SelectQueryExpression _selectQueryExpression;
        private ICaseExpression _currentCaseExpression;        
        private IDictionary<MemberInfo,IExpression> _memberExpressions = new Dictionary<MemberInfo, IExpression>();
        private IDictionary<string, IJoinExpression> _joinExpressions = new Dictionary<string, IJoinExpression>();

        public ExpressionTranslator(string connectionName) : base(connectionName)
        {
            
        }

        protected override ITranslateResult TranslateExpression(Expression expression, PropertyInfo property, IProjectionTarget outerProjectionTarget)
        {
            throw new NotImplementedException();
        }

        protected override ITranslateResult TranslateExpression(Expression expression)
        {
            return TranslateExpression(expression, null, false);
        }


        private void InitializeTranslateResult(Expression expression, ITranslateResult parentTranslateResult, bool isSubQuery)
        {
            _translateResult = new TranslateResult();
            _translateResult.Expression = expression;
            _translateResult.ProjectedType = expression.Type.GetGenericElementType();
            _translateResult.RootType = ResolveRootType(expression);
            _translateResult.TableInfo = MappingRepository[_translateResult.RootType].TableInfo;
            _translateResult.TypeMappingInfo = MappingRepository[_translateResult.RootType];
            _translateResult.ParentTranslateResult = parentTranslateResult;
            _translateResult.IsSubQuery = isSubQuery;            
            if (parentTranslateResult == null)
            {
                _translateResult.RootAlias = 0;
                _translateResult.CurrentAlias = -1;
                _translateResult.LoadExpressions = GetLoadExpressions(expression);
            }
            else
            {                
                _translateResult.RootAlias = parentTranslateResult.CurrentAlias + 1;
                _translateResult.CurrentAlias = parentTranslateResult.CurrentAlias;
                _translateResult.LoadExpressions = parentTranslateResult.LoadExpressions;
            }                        
        }
        


        /// <summary>
        /// Resolves the root <see cref="Type"/> of the expression currently being translated.
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        private Type ResolveRootType(Expression expression)
        {
            var queryable = QueryableResolver.ResolveFrom(expression);
            if (queryable != null)
                return queryable.ElementType;
            
            //No queryable present in the expression
            //This happens during translations of lambda expressions that is turned into sub queries
            var methodCallExpression = (expression as MethodCallExpression);
            
            if (methodCallExpression != null)
                return methodCallExpression.Arguments[0].Type.GetGenericElementType();            
            
            //No methodcall (Where, Select etc) present in the expression            
            return expression.Type.GetGenericElementType();
        }


        protected override ITranslateResult TranslateExpression(Expression expression, ITranslateResult parent, bool createSubQuery)
        {

            InitializeTranslateResult(expression,parent,createSubQuery);

            var methodCallExpression = (expression as MethodCallExpression);
            if (methodCallExpression == null)
            {                                             
                var fromClause = ExpressionFactory.Table(GetTableName(_translateResult.RootType),GetNextAlias());                                                
                _selectQueryExpression =
                  ServiceContainer.GetService<IQuery>().Select(CreateProjectionFromType(_translateResult.RootType));
                _selectQueryExpression.From(fromClause);
                _translateResult.DbExpression = _selectQueryExpression;                
            }
            else
            {
                _translateResult.DbExpression = Visit(expression);
            }

            ProjectPrimaryKeyFromRootType();

            if (createSubQuery)
                TransformToSubQuery(parent);    
            else
            {
                //TODO Apply the parent result 
                if (_translateResult.ParentTranslateResult != null)
                {
                    var resolver = ServiceContainer.GetService<ILoadExpressionResolver>();
                    var member = resolver.GetMemberInfo(expression, _translateResult.ParentTranslateResult.RootType);
                    ApplyOuterResult(_translateResult,(PropertyInfo)member.Member,_translateResult.ParentTranslateResult);
                }
                TranslateLoadExpressions();
            }      
                


            return _translateResult;


        }

        protected override ITranslateResult TranslateExpression(IRelationLoadTarget relationLoadTarget,IProjectionTarget outerProjectionTarget, ITranslateResult outerTranslateResult)
        {
            throw new NotImplementedException();
        }


        private IList<Expression> GetLoadExpressions(Expression expression)
        {
            var queryable = QueryableResolver.ResolveFrom(expression);            
            var loadExpressions =  ((ILoadOptions) queryable).Expressions;
            IList<Expression> result = new List<Expression>();
            foreach (var loadExpression in loadExpressions)
            {
                var lambdaExpression = StripQuotes(loadExpression) as LambdaExpression;
                if (lambdaExpression == null)
                    continue;
                result.Add(lambdaExpression.Body);
            }

            return result;
        }

        public void TransformToSubQuery(ITranslateResult parent)
        {
            if (parent == null)
                return;
            ((ISelectExpression) _selectQueryExpression).IsSubQuery = true;

            IForeignKeyInfo foreignKeyInfo =
                           _translateResult.TableInfo.ForeignKeys.Where(fk => fk.PrimaryTable == parent.TableInfo).First();
            foreach (var foreignkeyColumnInfo in foreignKeyInfo.Columns)
            {
                _selectQueryExpression.Where(ExpressionFactory.Column(GetCurrentAlias(), foreignkeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                    ExpressionFactory.Column(string.Format("t{0}", parent.RootAlias), foreignkeyColumnInfo.PrimaryColumnInfo.ColumnName));
            }
        }

        private void ApplyOuterResult(ITranslateResult targetResult, PropertyInfo outerProperty, ITranslateResult outerTranslateResult)
        {
                var targetSelectQuery = (SelectQueryExpression)targetResult.DbExpression;
                var outerExpression = RemoveProjectionFromOuterResult(outerTranslateResult);                                
                var foreignKeyInfo = outerTranslateResult.TypeMappingInfo.ColumnMappings[outerProperty].ForeignKeyInfo;

                foreach (var foreignkeyColumnInfo in foreignKeyInfo.Columns)
                {
                    if (outerTranslateResult.TypeMappingInfo.ColumnMappings[outerProperty].RelationType == CardinalityType.OneToMany)
                    {
                        outerExpression.Where(
                            ExpressionFactory.Column(string.Format("t{0}", targetResult.RootAlias),
                                                     foreignkeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                            ExpressionFactory.Column(string.Format("t{0}", outerTranslateResult.RootAlias),
                                                     foreignkeyColumnInfo.PrimaryColumnInfo.ColumnName));
                    }

                    if (outerTranslateResult.TypeMappingInfo.ColumnMappings[outerProperty].RelationType == CardinalityType.ManyToOne)
                    {
                            outerExpression.Where(
                                ExpressionFactory.Column(string.Format("t{0}", outerTranslateResult.RootAlias), foreignkeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                                ExpressionFactory.Column(string.Format("t{0}", targetResult.RootAlias), foreignkeyColumnInfo.PrimaryColumnInfo.ColumnName));                        
                    }

                }
                targetSelectQuery.Where(e => e.Exists(outerExpression));
        }

      


        private SelectQueryExpression RemoveProjectionFromOuterResult(ITranslateResult outerResult)
        {
            var query = ServiceContainer.GetService<IQuery>();

            var parentExpression = (ISelectExpression)outerResult.DbExpression;

            var selectQuery = query.Select(e => e.Constant(1)).From(parentExpression.FromExpression)
                .Where(parentExpression.WhereExpression);

            return selectQuery;
        }


        private void TranslateLoadExpressions()
        {            
            var resolver = ServiceContainer.GetService<ILoadExpressionResolver>();
            foreach (var loadExpression in _translateResult.LoadExpressions)
            {
                var member = resolver.GetMemberInfo(loadExpression, _translateResult.RootType);                                
                if (member != null && IsRelational(member.Member))
                {                    
                    var expressionTranslator = ServiceContainer.GetService<IExpressionTranslator>();                    
                    var translateResult = expressionTranslator.Translate(loadExpression, _translateResult, false);
                    ProjectForeignKey(_translateResult,translateResult,member.Member);
                    _translateResult.MemberResults.Add(member.Member, translateResult);
                }
            }
        }


        protected override IExpression VisitParameter(ParameterExpression expression)
        {
            return ((ISelectExpression) _selectQueryExpression).ProjectionExpression;
        }

        protected override IExpression VisitNew(NewExpression expression)
        {
            IList<IExpression> _projectionList = new List<IExpression>();
            for (int i = 0; i < expression.Arguments.Count; i++)
            {                
                //Experimental start
                var argument = expression.Arguments[i];
                if (argument is MethodCallExpression)
                {                                        
                    //TODO Turn into subquery when scalar value is excpected 
                    //Aggregate functions like SUM AND COUNT
                    if (((MethodCallExpression)argument).Method.Name == "Sum")
                    {

                        IExpressionTranslator translator = ServiceContainer.GetService<IExpressionTranslator>();
                        var subQueryResult = translator.Translate(argument, _translateResult, true);
                        var alias = GetPropertyNameFromMethodInfo(expression.Members[i]);
                        var dbExpression = ExpressionFactory.Alias(subQueryResult.DbExpression, alias);
                        _memberExpressions.Add(expression.Members[i], dbExpression);
                        _projectionList.Add(dbExpression);
                    }
                    else
                    {
                        _translateResult.LoadExpressions.Add(argument);                  
                    }
                }
                else//Experimental end
                
                
                
                _projectionList.Add(CreateAliasExpression(expression.Arguments[i],expression.Members[i]));
            }
            var list = ExpressionFactory.List(_projectionList);
            return list;
        }








        private IExpression CreateAliasExpression(Expression expression, MemberInfo memberInfo  )
        {
            var proposedAlias = GetPropertyNameFromMethodInfo(memberInfo);

            if (expression is NewExpression)
                return (Visit(expression));
            
            
            if (expression is MemberExpression)
            {
                var memberExpression = (MemberExpression) expression;
                var dbExpression = Visit(expression);
                if (memberExpression.Member.Name != proposedAlias)
                    dbExpression =  ExpressionFactory.Alias(dbExpression, proposedAlias);
                
                _memberExpressions.Add(memberInfo,dbExpression);
                return dbExpression;

            }            
            return ExpressionFactory.Alias(Visit(expression), proposedAlias);
        }


        private string GetPropertyNameFromMethodInfo(MemberInfo methodInfo)
        {            
            if (!methodInfo.Name.StartsWith("get_") && !methodInfo.Name.StartsWith("set_"))
                return methodInfo.Name;
            return methodInfo.Name.Substring(4);
        }




        public override void Initialize(IServiceRequestResult source)
        {
            base.Initialize(source);            
        }


        protected override IExpression VisitMemberInit(MemberInitExpression memberInitExpression)
        {
            IList<IExpression> projectionList = new List<IExpression>();
            foreach (var memberBinding in memberInitExpression.Bindings)
            {
                if (memberBinding.BindingType == MemberBindingType.Assignment)
                {
                    var memberAssignment = (MemberAssignment) memberBinding;
                    projectionList.Add(CreateAliasExpression(memberAssignment.Expression, memberAssignment.Member));                    
                }
            }
            var list = ExpressionFactory.List(projectionList);

            return list;
        }

        protected override IExpression VisitConditional(ConditionalExpression conditionalExpression)
        {
            //Check to see if an case expession is already being built
            if (_currentCaseExpression == null)
                _currentCaseExpression = ExpressionFactory.Case();

            var caseExpression = _currentCaseExpression;

            caseExpression.When(Visit(conditionalExpression.Test), Visit(conditionalExpression.IfTrue));

            if (conditionalExpression.IfFalse is ConditionalExpression)
            {
                if (caseExpression.ElseExpression == null)
                    Visit(conditionalExpression.IfFalse);
            }
            else
                caseExpression.Else(Visit(conditionalExpression.IfFalse));

            

            if (!(conditionalExpression.IfTrue is ConditionalExpression) && !(conditionalExpression.IfFalse is ConditionalExpression))                           
                _currentCaseExpression = null;
            
                
            return caseExpression;
        }





        protected override IExpression VisitUnary(UnaryExpression expression)
        {
            if (expression.Type == typeof(bool) && expression.NodeType == ExpressionType.Not)
                return ExpressionFactory.Not(Visit(expression.Operand));
            
            
            return Visit(expression.Operand);
        }

        


        protected override IExpression VisitMethodCall(MethodCallExpression methodCallExpression)
        {
            if (methodCallExpression.Method.Name == "Where")
            {
                
                var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                
                var fromClause = Visit(methodCallExpression.Arguments[0]);
                if (fromClause is SelectQueryExpression)
                {                    
                    ((SelectQueryExpression) fromClause).Where(Visit(lambdaExpression));
                    return fromClause;
                }
             
                _selectQueryExpression =
                ServiceContainer.GetService<IQuery>().Select(CreateProjectionFromType(_translateResult.RootType));                                
                _selectQueryExpression.From(fromClause);


                _selectQueryExpression.Where(Visit(lambdaExpression));

                return _selectQueryExpression;
            }

            if (methodCallExpression.Method.Name.StartsWith("OrderBy") || methodCallExpression.Method.Name.StartsWith("ThenBy"))
            {
                var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                var fromClause = Visit(methodCallExpression.Arguments[0]);

                if (fromClause is SelectQueryExpression)
                {
                    if (methodCallExpression.Method.Name == "OrderBy" || methodCallExpression.Method.Name == "ThenBy")
                        ((SelectQueryExpression)fromClause).OrderBy(Visit(lambdaExpression));
                    else
                        ((SelectQueryExpression)fromClause).OrderBy(Visit(lambdaExpression),SortDirection.Descending);
                    return fromClause;
                }


                _selectQueryExpression =
                ServiceContainer.GetService<IQuery>().Select(CreateProjectionFromType(_translateResult.RootType));                                
                _selectQueryExpression.From(fromClause);
                if (methodCallExpression.Method.Name == "OrderBy" || methodCallExpression.Method.Name == "ThenBy")
                    _selectQueryExpression.OrderBy(Visit(lambdaExpression));
                else
                    _selectQueryExpression.OrderBy(Visit(lambdaExpression),SortDirection.Descending);

                return _selectQueryExpression;
            }

            if (methodCallExpression.Method.Name == "Count")
            {
                IExpression whereExpression = null;

                var fromClause = Visit(methodCallExpression.Arguments[0]);

                if (methodCallExpression.Arguments.Count == 2)
                {
                    var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                    whereExpression = Visit(lambdaExpression);
                }
                    
                                
                if (fromClause is SelectQueryExpression)
                {
                    ((ISelectExpression) fromClause).ProjectionExpression = ExpressionFactory.Count();
                    if (whereExpression != null)
                        ((SelectQueryExpression)fromClause).Where(whereExpression);
                    return fromClause;
                }

                _selectQueryExpression =
                 ServiceContainer.GetService<IQuery>().Select(ExpressionFactory.Count());
                _selectQueryExpression.From(fromClause);

                if (whereExpression != null)
                    _selectQueryExpression.Where(whereExpression);

                return _selectQueryExpression;

            }




            if (methodCallExpression.Method.Name == "First")
            {
                var fromClause = Visit(methodCallExpression.Arguments[0]);
                if (methodCallExpression.Arguments.Count == 1)
                {
                    
                    if (fromClause is SelectQueryExpression)
                    {
                        ((SelectQueryExpression) fromClause).Take(1);
                        return fromClause;
                    }
                    
                    _selectQueryExpression =
                    ServiceContainer.GetService<IQuery>().Select(CreateProjectionFromType(_translateResult.RootType));
                    _selectQueryExpression.From(fromClause).Take(1);
                    return _selectQueryExpression;                
                }
                
                var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                if (fromClause is SelectQueryExpression)
                {
                    ((SelectQueryExpression)fromClause).Take(1);
                    ((SelectQueryExpression)fromClause).Where(Visit(lambdaExpression));
                    return fromClause;
                }

                _selectQueryExpression =
                    ServiceContainer.GetService<IQuery>().Select(CreateProjectionFromType(_translateResult.RootType));
                _selectQueryExpression.From(fromClause);
                _selectQueryExpression.Where(Visit(lambdaExpression)).Take(1);

                return _selectQueryExpression;
            }

            if (methodCallExpression.Method.Name == "Sum")
            {
                
                if ((IExpression)_selectQueryExpression != null)
                {
                    var expressionTranslator = ServiceContainer.GetService<IExpressionTranslator>();
                    var subQueryResult = expressionTranslator.Translate(methodCallExpression, _translateResult, true);
                    return subQueryResult.DbExpression;
                }
                
                
                var fromClause = Visit(methodCallExpression.Arguments[0]);

                if (fromClause is SelectQueryExpression)
                {
                    ((ISelectExpression) fromClause).ProjectionExpression =
                        ExpressionFactory.Sum(((ISelectExpression) fromClause).ProjectionExpression);
                    return fromClause;
                }

                var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                _selectQueryExpression =
                    ServiceContainer.GetService<IQuery>().Select(ExpressionFactory.Sum(Visit(lambdaExpression)));
                _selectQueryExpression.From(fromClause);
                return _selectQueryExpression;
            }

            if (methodCallExpression.Method.Name == "Any")
            {
                if ((IExpression)_selectQueryExpression != null)
                {
                    var expressionTranslator = ServiceContainer.GetService<IExpressionTranslator>();
                    var subQueryResult = expressionTranslator.Translate(methodCallExpression, _translateResult, true);                    
                    return ExpressionFactory.Exists((SelectQueryExpression)subQueryResult.DbExpression);
                }

                var fromClause = Visit(methodCallExpression.Arguments[0]);


                if (fromClause is SelectQueryExpression)
                {
                    ((ISelectExpression) fromClause).ProjectionExpression =
                        ExpressionFactory.Constant(1);
                    return fromClause;
                }

                
                _selectQueryExpression =
                    ServiceContainer.GetService<IQuery>().Select(ExpressionFactory.Constant(1));
                _selectQueryExpression.From(fromClause);

                if (methodCallExpression.Arguments.Count == 2)
                {
                    var lambdaExpression =
                        ((LambdaExpression) StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                    _selectQueryExpression.Where(Visit(lambdaExpression));
                }
                return _selectQueryExpression;
            }


            
            if (methodCallExpression.Method.Name == "Select")
            {
                var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();

                


                var fromClause = Visit(methodCallExpression.Arguments[0]);
                if (fromClause is SelectQueryExpression)
                {
                    ((ISelectExpression)fromClause).ProjectionExpression = Visit(lambdaExpression);
                    return fromClause;
                }
                else
                {
                    _selectQueryExpression =
                        ServiceContainer.GetService<IQuery>().Select(Visit(lambdaExpression));
                    _selectQueryExpression.From(fromClause);
                    return _selectQueryExpression;
                }
                
            }



            if (methodCallExpression.Method.Name == "Contains")
            {               
                if (methodCallExpression.Arguments.Count == 2)
                {
                    var values = ((ConstantExpression)methodCallExpression.Arguments[0]).Value;
                    var valueList = ConvertListToObjectArray((IEnumerable)values);                        
                    var expression = ExpressionFactory.In(Visit(methodCallExpression.Arguments[1]), valueList);
                    return expression;
                }
                if (methodCallExpression.Arguments.Count == 1)
                {                    
                    //May happen when passing in e.g. IList<string> rather than string[];
                    if (methodCallExpression.Object is ConstantExpression)
                    {
                        var values = ((ConstantExpression) methodCallExpression.Object).Value;
                        var valueList = ConvertListToObjectArray((IEnumerable) values);                        
                        var expression = ExpressionFactory.In(Visit(methodCallExpression.Arguments[0]), valueList.ToArray());
                        return expression;
                    }
                    else
                    {                        
                        var expression = ExpressionFactory.Contains(Visit(methodCallExpression.Object), Visit(methodCallExpression.Arguments[0]));
                        return expression;    
                    }
                    
                }
            }


            throw new NotSupportedException(string.Format("Method {0} is not supported",methodCallExpression.Method.Name));
        }

        private object[] ConvertListToObjectArray(IEnumerable list)
        {
            IList<object> valueList = new List<object>();
            foreach (var element in list)
            {                                
                    valueList.Add(element);                                
            }
            return valueList.ToArray();
        }
        




        protected override IExpression VisitConstant(ConstantExpression constantExpression)
        {
            IQueryable queryable = constantExpression.Value as IQueryable;
            if (queryable != null)
            {
                if (((ISelectExpression)_selectQueryExpression) == null)
                    Console.WriteLine();
                
                var tableName = GetTableName(queryable.ElementType);
                var alias = GetNextAlias();
                _translateResult.RootAlias = _translateResult.CurrentAlias;
                _translateResult.RootType = queryable.ElementType;
                _translateResult.TableInfo = MappingRepository[_translateResult.RootType].TableInfo;
                return ExpressionFactory.Table(tableName).As(alias);
            }
                

            return ExpressionFactory.Constant(constantExpression.Value);
        }

     
        


        private string GetTableName(Type type)
        {
            type = type.GetGenericElementType();
            return  MappingRepository[type].TableInfo.TableName;
        }



        protected override IExpression VisitLambda(LambdaExpression expression)
        {
            return Visit(expression.Body);
        }


        protected override IExpression VisitMemberAccess(MemberExpression expression)
        {
            
            
            if (IsRelational(expression.Member))
                return ExpressionFactory.Table(GetTableName(((PropertyInfo) expression.Member).PropertyType),
                                               GetNextAlias());



            if (expression.Member.DeclaringType != _translateResult.RootType && IsRelational(expression.Member.DeclaringType))
            {
                CreateJoinExpression(expression);
                return ExpressionFactory.Column(GetAliasFromJoinExpression(expression.Expression.ToString()), expression.Member.Name);
            }
            
            
            var getMethod = ((PropertyInfo) expression.Member).GetGetMethod();            
            if (_memberExpressions.ContainsKey(getMethod))            
            {
                IAliasExpression aliasExpression = (_memberExpressions[getMethod] as IAliasExpression);
                if (aliasExpression != null)
                    return aliasExpression.Target;
                return _memberExpressions[getMethod];
            }                
            return ExpressionFactory.Column(GetCurrentAlias(), expression.Member.Name);        
        }

        private void CreateJoinExpression(MemberExpression memberExpression)
        {
            
            
            Stack<MemberExpression> memberExpressions = new Stack<MemberExpression>();
            
            while(memberExpression.Expression is MemberExpression)
            {
                //Do not create the join if already created
                if (!_joinExpressions.ContainsKey(memberExpression.Expression.ToString()))
                    memberExpressions.Push((MemberExpression)memberExpression.Expression);
                memberExpression = (MemberExpression) memberExpression.Expression;
            }

            foreach (var expression in memberExpressions)
            {                


                var propertyInfo = (PropertyInfo) expression.Member;
                                           
                var parentTypeMappingInfo = MappingRepository[propertyInfo.DeclaringType];
                var foreignKeyInfo = parentTypeMappingInfo.ColumnMappings[propertyInfo].ForeignKeyInfo;
                var currentAlias = GetAliasFromJoinExpression(expression.Expression.ToString());
                var nextAlias = GetNextAlias();


                IExpression criteriaExpression = null;
                foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                {                    
                    var equalsExpression =
                        ExpressionFactory.Column(currentAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                        ExpressionFactory.Column(nextAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName);

                    if (criteriaExpression == null)
                        criteriaExpression = equalsExpression;
                    else
                        criteriaExpression = (ExpressionBase)criteriaExpression & equalsExpression;
                    
                }

                var joinType = DetermineJoinType(foreignKeyInfo);

                var joinExpression = ExpressionFactory.Join(joinType,
                                                            ExpressionFactory.Table(
                                                                foreignKeyInfo.PrimaryTable.TableName,nextAlias),
                                                            criteriaExpression);
                
                
                var selectExpression = (ISelectExpression) _selectQueryExpression;

                var compositeExpression = ExpressionFactory.Composite(selectExpression.FromExpression,
                                                                              joinExpression);

                selectExpression.FromExpression = compositeExpression;

                _joinExpressions.Add(expression.ToString(), (IJoinExpression) joinExpression);

            }           
        }


        private JoinType DetermineJoinType(IForeignKeyInfo foreignKeyInfo)
        {
            if (foreignKeyInfo.Columns.Count(c => c.ForeignColumnInfo.IsNullable) > 0)            
                return JoinType.LeftOuter;

            return JoinType.Inner;
        }


        private string GetAliasFromJoinExpression(string name)
        {
            if (_joinExpressions.ContainsKey(name))
                return ((IAliasExpression)_joinExpressions[name].Target).Alias;
            
            return GetCurrentAlias();
        }


        protected override IExpression VisitBinary(BinaryExpression binaryExpression)
        {
            switch (binaryExpression.NodeType)
            {
                case ExpressionType.Equal:
                    return ExpressionFactory.Eq(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.GreaterThan:
                    return ExpressionFactory.GreaterThan(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.GreaterThanOrEqual:
                    return ExpressionFactory.GreaterOrEqual(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.LessThan:
                    return ExpressionFactory.LessThan(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.LessThanOrEqual:
                    return ExpressionFactory.LessOrEqual(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    return ExpressionFactory.Or(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    return ExpressionFactory.And(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Add:
                    return ExpressionFactory.Add(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Subtract:
                    return ExpressionFactory.Subtract(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Multiply:
                    return ExpressionFactory.Multiply(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Divide:
                    return ExpressionFactory.Divide(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                default:
                    throw new NotSupportedException(string.Format("BinaryExpression ({0}) not supported",binaryExpression.NodeType));
            }                        



        }


        private IExpression CreateProjectionFromType(Type type)
        {
            var tableMappingInfo = MappingRepository[type];
            var alias = GetCurrentAlias();
            var projection = tableMappingInfo.ColumnMappings.Values.Where(cm => cm.ColumnInfo != null).Select(
                cm => (IExpression)ExpressionFactory.Column(alias, cm.ColumnInfo.ColumnName)).ToList();
            return ExpressionFactory.List(projection);
        }
        

        private string GetCurrentAlias()
        {
            return string.Format("t{0}", _translateResult.CurrentAlias);
        }


        private string GetNextAlias()
        {
            _translateResult.CurrentAlias++;            
            return GetCurrentAlias();
        }

        private bool ProjectionIsAggregateMethod()
        {
            var projection = ((ISelectExpression)_selectQueryExpression).ProjectionExpression;            
            if (projection is IAggregateExpression)
                return true;
            return false;
        }



        private void ProjectPrimaryKeyFromRootType()
        {
            if (_translateResult.IsSubQuery || ProjectionIsAggregateMethod())
                return;
        
            var projection = ((ISelectExpression) _selectQueryExpression).ProjectionExpression;
            
            
            IList<IExpression> projectionList = new List<IExpression>();

            var tableMappingInfo = MappingRepository[_translateResult.RootType];            
            var primaryKeyColumns = tableMappingInfo.TableInfo.PrimaryKey.Columns.Select(c => ExpressionFactory.Column(string.Format("t{0}",_translateResult.RootAlias), c.ColumnName)).
                ToList();

            foreach (var primaryKeyColumn in primaryKeyColumns)
            {
                if (!IsProjected(primaryKeyColumn,projection))                
                    projectionList.Add(primaryKeyColumn);
                
            }

            if (projection is IExpressionList)
                ((IExpressionList) projection).Expressions.ForEach(projectionList.Add);
            else
                projectionList.Add(projection);


            ((ISelectExpression) _selectQueryExpression).ProjectionExpression = ExpressionFactory.List(projectionList);
        }


        private void ProjectForeignKey(ITranslateResult parentResult, ITranslateResult targetResult, MemberInfo memberInfo)
        {
            var propertyInfo = (PropertyInfo)memberInfo;
            
            var foreignKeyInfo = parentResult.TypeMappingInfo.ColumnMappings[propertyInfo].ForeignKeyInfo;

            ITranslateResult projectionTarget;

            IEnumerable<ExpressionBase> foreignKeyColumns; 

            if (parentResult.TypeMappingInfo.ColumnMappings[propertyInfo].RelationType == CardinalityType.ManyToOne)
            {
                projectionTarget = parentResult;
                foreignKeyColumns = foreignKeyInfo.Columns.Select(fkc =>
                    ExpressionFactory.Column(string.Format("t{0}", parentResult.RootAlias),
                                             fkc.ForeignColumnInfo.ColumnName)).ToList();

            }
            else
            {
                projectionTarget = targetResult;
                foreignKeyColumns = foreignKeyInfo.Columns.Select(fkc =>
                    ExpressionFactory.Column(string.Format("t{0}", targetResult.RootAlias),
                                             fkc.PrimaryColumnInfo.ColumnName)).ToList();
            }




            var projection = ((ISelectExpression)projectionTarget.DbExpression).ProjectionExpression;
                IList<IExpression> projectionList = new List<IExpression>();
                                                
            var primaryKeyCount = projectionTarget.TableInfo.PrimaryKey.Columns.Count;

            if (projection is IExpressionList)
                ((IExpressionList)projection).Expressions.ForEach(projectionList.Add);
            else
                projectionList.Add(projection);

            foreach (var foreignKeyColumn in foreignKeyColumns)
            {
                if (!IsProjected(foreignKeyColumn, projection))
                    projectionList.Insert(primaryKeyCount, foreignKeyColumn);
            }

            ((ISelectExpression)projectionTarget.DbExpression).ProjectionExpression = ExpressionFactory.List(projectionList);
        }


       


        private bool IsProjected(IExpression expression, IExpression projection)
        {
            IExpressionList list = (projection as IExpressionList);
            if (list == null)
                return expression.Equals(projection);


            foreach (var dbExpression in list.Expressions)
            {
                if (dbExpression is IExpressionList)
                {
                    if (IsProjected(expression, dbExpression))
                        return true;
                }
                else
                {
                    if (dbExpression.Equals(expression))
                        return true;

                }
            }
            return false;            
        }


        private bool IsRelational(MemberInfo memberInfo)
        {
            Type type;

            PropertyInfo propertyInfo = (memberInfo as PropertyInfo);
            if (propertyInfo == null)
            {
                type = (memberInfo as Type);
                if (type == null)
                    return false;
            }
            else
                type = propertyInfo.PropertyType;

            var elementType = type.GetGenericElementType();
            if (elementType.IsSystemType() && !elementType.IsEnumerable())
                return false;
            return true;            
        }

        public override string ToString()
        {
            if (_translateResult != null && _translateResult.Expression != null)
                return _translateResult.Expression.ToString();
            return base.ToString();
        }
    }
}
